# Basic tag code that can be used as base for more advanced tag implementations
#===============================================================================
# Functions
#===============================================================================
# Returns tagged in char id
[Function TagGetPartnerId(add) partnerId]
let rootId = getPlayerId(teamSide);
let partnerCnt = 0;
let partnerId = id + $add;
for i = 0; numPartner; 1 {
	if $partnerId > $rootId + numPartner {
		let partnerId = $rootId;
	} else if $partnerId < $rootId {
		let partnerId = $rootId + numPartner;
	}
	if playerId($partnerId),alive = 0 && $partnerCnt < numPartner {
		let partnerCnt = $partnerCnt + 1;
		let partnerId = $partnerId + $add;
	} else {
		break;
	}
}
if $partnerCnt >= numPartner {
	let partnerId = 0;
}

# Decides if AI switch should happen, returns tagged in char id (function can be overridden locally)
# TODO: Balance default AI switch scaling.
[Function TagAISwitch(nextPartnerId, previousPartnerId) partnerId]
let partnerId = 0;
if const(TagCommandAIChance) >= randomRange(1, 100) {
	if command = "TagShiftFwd" {
		let partnerId = $nextPartnerId;
	} else if command = "TagShiftBack" {
		let partnerId = $previousPartnerId;
	}
}

# tagIn Switch explod
[Function TagSwitchExplod()]
let anim = const(FxTagSwitchAI);
if aiLevel = 0 {
	if gameMode = "versuscoop" {
		let anim = $anim + playerNo;
	} else if gameMode = "netplaysurvivalcoop" || gameMode = "netplayteamcoop" || gameMode = "survivalcoop" || gameMode = "teamcoop" {
		if teamSide = 1 {
			let anim = $anim + playerNo / 2 + 1;
		} else {
			let anim = $anim + playerNo / 2;
		}
	} else {
		let anim = $anim + teamSide;
	}
}
explod{
	# Do note all anims and playsnds on this file are called from gofx.def, not fightfx.air/common.snd
	anim: GO $anim;
	postype: p1;
	pos: 0, floor((const(size.head.pos.y) - const(size.ground.front) / 2 - const(size.ground.back) / 2));
	sprpriority: 5;
	ownpal: 1;
	facing: facing;
	scale: 0.5, 0.5;
	bindtime: -1;
	supermovetime: -1;
	pausemovetime: -1;
	removeongethit: 1;
	ignorehitpause: 1;
}

#===============================================================================
# StateTagEnteringScreen
#===============================================================================
[StateDef const(StateTagEnteringScreen); 
	type: S; movetype: H; physics: N;
	anim: const(AnimTagEnteringScreen);
	sprpriority: 2; ctrl: 0;
]

screenBound{value: 0; movecamera: 1, 1; stagebound: 0}
assertSpecial{flag: noTurnTarget}

if time = 0 {
	if facing != player(teamLeader),facing {
		turn{}
	}
	# set starting pos
	posSet{x: const240p(pos x * localScale - (backEdgeBodyDist * localScale + 90) * facing); y: 0}
	# store distance to switch initiation position
	map(_iksys_tagSwitchDist) := abs(pos x - player(teamLeader),pos x);
	# set movement
	velSet{x: min(const240p(const(TagInVelX)), map(_iksys_tagSwitchDist) / const(TagInTime)); y: 0}
	# change active player assignment
	map(_iksys_tagLastId) := player(teamLeader),id;
	tagIn{leader: playerNo}
	call TagSwitchExplod();
} else {
	mapAdd{map: "_iksys_tagSwitchDist"; value: -vel x}
}

# continue until reaching place where the switch was initiated, but stop in front of enemy
if backEdgeBodyDist >= 0 && (floor(map(_iksys_tagSwitchDist)) <= 0 || p2BodyDist X < const240p(30)) {
	velSet{x: 0; y: 0}
	selfState{value: const(StateStand); ctrl: 1}
}

#===============================================================================
# StateTagLeavingScreen
#===============================================================================
[StateDef const(StateTagLeavingScreen);
	type: S; movetype: I; physics: N;
	anim: const(AnimTagLeavingScreen); sprpriority: -2;
	velset: 0, 0; facep2: 1;
	ctrl: 0;
]

screenBound{value: 0; movecamera: 0, 0; stagebound: 0}

if time = 0 {
	turn{}
}

velSet{x: min(ifElse(const(velocity.run.fwd.x) != 0, const(velocity.run.fwd.x), const(velocity.walk.fwd.x) * 2), const240p(const(TagInVelX))); y: 0}

if backEdgeDist < -const240p(160) || frontEdgeDist < -const240p(160) {
	selfState{value: const(StateTagWaitingOutside)}
}

#===============================================================================
# StateTagWaitingOutside
#===============================================================================
[StateDef const(StateTagWaitingOutside);
	type: A; movetype: I; physics: N;
	anim: const(AnimStanding);
	velset: 0, 0; ctrl: 0;
]

screenBound{value: 0; movecamera: 0, 0; stagebound: 0}
assertSpecial{flag: invisible; flag2: noAutoTurn}

# force standby character to always stay outside visible area
posSet{
	x: (player(teamLeader),pos x - player(teamLeader),screenPos x - const240p(160)) * -facing;
	y: player(teamLeader),pos y - player(teamLeader),screenPos y - const240p(160);
}

# Red Life regeneration
if life < redLife && (time % const(TagRedLifeRegenFrames)) = 0 { # every 30 frames (0.5s) by default
	lifeAdd{value: min(redLife - life, ceil(lifeMax * const(TagRedLifeRegenPercent))); absolute: 1} # 0.5% lifeMax by default
}

#===============================================================================
# StateTagJumpingIn
#===============================================================================
[StateDef const(StateTagJumpingIn);
	type: A; movetype: I; physics: N;
	anim: const(AnimTagJumpingIn);
	sprpriority: 2; ctrl: 0;
]

if roundState = 3 {
	screenBound{value: 0; movecamera: 0, 0; stagebound: 0}
	playerPush{value: 0}
} else {
	screenBound{value: 0; movecamera: 1, 1; stagebound: 0}
	assertSpecial{flag: noTurnTarget}
}

# the character cannot be hit until he has fully entered the stage
if backEdgeBodyDist <= 0 {
	notHitBy{value: SCA; time: 2}
}

# regain control right after entering screen, in case of active player loss
if !ctrl && backEdgeBodyDist >= 0 {
	ctrlSet{value: 1}
}

if time = 0 {
	if facing != player(teamLeader),facing {
		turn{}
	}
	# set starting pos
	posSet{x: const240p(pos x * localScale - (backEdgeBodyDist * localScale + 90) * facing); y: 0}
	# change active player assignment if the battle still goes on
	if roundState = 2 {
		map(_iksys_tagLastId) := player(teamLeader),id;
		tagIn{leader: playerNo}
		call TagSwitchExplod();
	}
	if roundState = 2 {
		velSet{x: const240p(4); y: -const240p(8.75)}
	} else {
		velSet{x: const240p(8.0 - 0.75 * (playerno - teamside)); y: -const240p(8.75)}
	}
} else {
	gravity{}
	if vel y > 0 && pos y >= 0 {
		velSet{x: 0; y: 0}
		selfState{value: const(StateTagLanding)}
	}
}

#===============================================================================
# StateTagLanding
#===============================================================================
[StateDef const(StateTagLanding);
	type: S; movetype: I; physics: N;
	anim: const(AnimTagLanding);
	sprpriority: 2; ctrl: 0;
]

if time = 0 {
	posSet{y: 0}
}

if animTime >= 0 {
	selfState{value: const(StateStand); ctrl: 1}
}

#===============================================================================
# Global states
#===============================================================================
[StateDef -2]

if !const(Default.Enable.Tag) || isHelper || teamSide = 0 {
	# do nothing, global code disabled locally or executed by helper/stage
} else if roundState = 0 && teamMode = Tag {
	map(_iksys_tagActive) := 1;
	map(_iksys_tagLastId) := 0;
	map(_iksys_tagPartnerId) := 0;
	if playerNo = teamLeader {
		map(_iksys_tagIntroFlag) := 1;
	} else {
		map(_iksys_tagIntroFlag) := 0;
		screenBound{value: 0; movecamera: 0, 0}
	}
	map(_iksys_tagSwitchCooldown) := 0;
} else if map(_iksys_tagActive) = 1 {
	# fix characters escaping state 5030
	if !alive {
		ctrlSet{value: 0}
		assertSpecial{flag: noailevel}
	}
	# fix erratic camera during intros
	if roundState = 1 && playerNo != teamLeader {
		screenBound{value: 0; movecamera: 0, 0}
	}
	# disable tag over win poses
	if roundState = 4 {
		map(_iksys_tagActive) := 0;
	# alive players
	} else if alive {
		let beingHit = 0;
		ignoreHitPause if moveType = H
			|| getHitVar(isBound) != 0
			|| stateNo = const(StateDownedGetHit_gettingUp) ||
			stateNo = [const(StateAirGetHit_fallRecoveryOnGroundStillFalling), const(StateAirGetHit_fallRecoveryInAir)]
		{
			let beingHit = 1;
		}
		# active player
		if playerNo = teamLeader {
			# delay next check if cooldown is active
			if map(_iksys_tagSwitchCooldown) > 0 {
				mapAdd{map: "_iksys_tagSwitchCooldown"; value: -1}
			# not being hit; standing idle or at least on the ground
			} else if $beingHit = 0 && (stateNo = const(StateStand) || pos y >= 0) {
				# last active player has left the screen or died
				if map(_iksys_tagLastId) = 0
					|| playerId(map(_iksys_tagLastId)),stateNo = playerId(map(_iksys_tagLastId)),const(StateTagWaitingOutside)
					|| playerId(map(_iksys_tagLastId)),alive = 0
				{
					# get partner id in case of tag command
					let partnerId = 0;
					let nextPartnerId = call TagGetPartnerId(1);
					let previousPartnerId = call TagGetPartnerId(-1);
					if aiLevel > 0 {
						# id is remembered by AI player, switch will happen at first frame that meets switch conditions
						if map(_iksys_tagPartnerId) = 0 {
							let partnerId = call TagAISwitch($nextPartnerId, $previousPartnerId);
							map(_iksys_tagPartnerId) := $partnerId;
						}
						let partnerId = map(_iksys_tagPartnerId);
					} else if command = "TagShiftFwd" {
						let partnerId = $nextPartnerId;
					} else if command = "TagShiftBack" {
						let partnerId = $previousPartnerId;
					}
					# having control, command buffered, partner standby
					if ctrl && $partnerId > 0 && playerId($partnerId),stateNo = playerId($partnerId),const(StateTagWaitingOutside) {
						mapSet{map: "_iksys_tagSwitchCooldown"; value: playerId($partnerId),const(TagSwitchCooldown); redirectid: $partnerId}
						map(_iksys_tagPartnerId) := 0;
						lifebarAction{spr: const(MsgPartnerChange), 0; top: 1}
						tagOut{}
						ctrlSet{value: 0}
						assertSpecial{flag: noHardcodedKeys; flag2: noInput}
						tagIn{stateno: playerId($partnerId),const(StateTagEnteringScreen); redirectid: $partnerId}
					}
				}
			}
		# partners (not hit)
		} else if $beingHit = 0 {
			# on screen, at the round start / during round
			if roundState = 2
				&& (ctrl || standby || map(_iksys_tagIntroFlag) = 0)
				&& (stateNo = const(StateStand) ||
				(pos y >= 0 && stateNo != [const(StateTagEnteringScreen), const(StateTagWaitingOutside)]))
			{
				map(_iksys_tagIntroFlag) := 1;
				tagOut{stateno: const(StateTagLeavingScreen)}
			# tagged out, at the round end
			} else if roundState = 3 {
				assertSpecial{flag: noInput}
				if stateNo = const(StateTagWaitingOutside) {
					tagIn{stateno: const(StateTagJumpingIn)}
				}
			}
		}
	# active player died and hit the ground
	} else if playerNo = teamLeader
		&& stateNo = [const(StateDownedGetHit_hitGroundFromFall), const(StateDownedGetHit_lyingDefeated)]
	{
		map(_iksys_tagActive) := 0;
		# bring next team member
		let partnerId = call TagGetPartnerId(1);
		if $partnerId > 0 {
			mapSet{map: "_iksys_tagSwitchCooldown"; value: playerId($partnerId),const(TagSwitchCooldown); redirectid: $partnerId}
			lifebarAction{spr: const(MsgPartnerChange), 0; top: 1}
			tagOut{}
			tagIn{stateno: playerId($partnerId),const(StateTagJumpingIn); redirectid: $partnerId}
		}
	}
}
